﻿using System.Reflection;
using Dyao.Book.Contract.Serialization;
using Dyao.Book.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

namespace Dyao.Book.Web.Extensions
{
    public class AdditionDataFormatterContractResolver : CamelCasePropertyNamesContractResolver
    {
        private static readonly string _defaultDescriptionNameFormat = "{0}Desc";
        private readonly AdditionDataFormatterContractResolverOptions _options;

        public AdditionDataFormatterContractResolver(AdditionDataFormatterContractResolverOptions options)
        {
            this._options = options;
        }

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var jsonProperty = base.CreateProperty(member, memberSerialization);

            CreateValueProvider(member, memberSerialization, jsonProperty);
            CreateConverter(member, memberSerialization, jsonProperty);

            return jsonProperty;
        }

        protected virtual void CreateValueProvider(MemberInfo member, MemberSerialization memberSerialization, JsonProperty jsonProperty)
        {
            var propertyType = jsonProperty.PropertyType;
            var attributes = jsonProperty.AttributeProvider.GetAttributes(true);

            var hasEmptyAttr = TryGetAttribute(attributes, out ConvertNullToEmptyAttribute emptyAttribute);

            if (((hasEmptyAttr && emptyAttribute.IsConvetNullToEmpty) || (!hasEmptyAttr && _options.IsConvertStringToEmpty))
                && propertyType == typeof(string))
            {
                jsonProperty.ValueProvider = new StringValueProvider(member, true);
            }
            else if (((hasEmptyAttr && emptyAttribute.IsConvetNullToEmpty) || (!hasEmptyAttr && _options.IsConvetListToEmpty))
                && (propertyType.IsArray || propertyType.IsGenericType))
            {
                Type itemType = null;
                if (jsonProperty.PropertyType.IsArray && propertyType.GetArrayRank() == 1)//一维数组
                {
                    var gType = propertyType.GetInterfaces().FirstOrDefault(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(IList<>));
                    if (gType != null && gType.GenericTypeArguments.Length > 0)
                    {
                        itemType = gType.GenericTypeArguments[0];
                    }
                }
                else if (propertyType.IsGenericType
                    && (propertyType.GetGenericTypeDefinition() == typeof(IList<>) || propertyType.GetGenericTypeDefinition() == typeof(List<>)))
                {
                    itemType = propertyType.GenericTypeArguments[0];
                }

                if (itemType != null)
                {
                    var valueProviderType = typeof(ListValueProvider<>).MakeGenericType(itemType);
                    var valueProvider = Activator.CreateInstance(valueProviderType, new object[] { member, true });
                    jsonProperty.ValueProvider = (IValueProvider)valueProvider;
                }
            }
        }

        protected virtual void CreateConverter(MemberInfo member, MemberSerialization memberSerialization, JsonProperty jsonProperty)
        {
            var attributes = jsonProperty.AttributeProvider.GetAttributes(true);
            var underlyingProertyType = UnwrapNullableType(jsonProperty.PropertyType);
            var converter = jsonProperty.Converter;
            IAdditionDataFormatter formatter = null;

            if (underlyingProertyType.IsEnum)
            {
                if (converter == null && TryGetAttribute<StringEnumAttribute>(attributes, out var stringEnumAttribute) && stringEnumAttribute.IsStringValue)
                {
                    converter = new StringEnumConverter();
                }

                if ((!TryGetAttribute(attributes, out ShowEnumDescriptionAttribute enumDescriptionAttribute)) && _options.IsAddEnumDescription)
                {
                    enumDescriptionAttribute = new ShowEnumDescriptionAttribute()
                    {
                        NameFormat = _options.EnumDescriptionNameFormat,
                    };
                }

                if (enumDescriptionAttribute != null && enumDescriptionAttribute.FormatEnabled)
                {
                    var valueformatter = new EnumDescriptionValueFormatter();
                    var nameFormat = GetOrDefault(enumDescriptionAttribute.NameFormat, _defaultDescriptionNameFormat);
                    formatter = new DefaultAdditionDataFormatter(valueformatter, nameFormat);
                }
            }
            else if (underlyingProertyType == typeof(bool))
            {
                if (!TryGetAttribute(attributes, out ShowBoolDescriptionAttribute boolAttribute) && _options.IsAddBoolDescription)
                {
                    boolAttribute = new ShowBoolDescriptionAttribute()
                    {
                        NameFormat = _options.BoolDescriptionNameFormat,
                    };
                }

                if (boolAttribute != null && boolAttribute.FormatEnabled)
                {
                    var valueFormatter = new BoolDescriptionValueFormatter(boolAttribute.TrueText, boolAttribute.FalseText);
                    var nameFormat = GetOrDefault(boolAttribute.NameFormat, _defaultDescriptionNameFormat);
                    formatter = new DefaultAdditionDataFormatter(valueFormatter, nameFormat);
                }
            }
            else if (underlyingProertyType == typeof(DateTime) && converter == null)
            {

                if (TryGetAttribute(attributes, out DatetimeFormatAttribute datetimeAttriubte) && datetimeAttriubte.FormatEnabled)
                {
                    converter = new IsoDateTimeConverter
                    {
                        DateTimeFormat = datetimeAttriubte.Format,
                        Culture = datetimeAttriubte.Culture
                    };
                }
            }

            if (formatter != null)
            {
                jsonProperty.Converter = new CompositeFormatterJsonConverter(formatter, converter);
            }
            else
            {
                jsonProperty.Converter = converter;
            }
        }

        private static bool TryGetAttribute<T>(IList<Attribute> attributes, out T attr)
            where T : Attribute
        {
            attr = (T)attributes?.FirstOrDefault(a => a is T);
            return attr != null;
        }

        protected static Type UnwrapNullableType(Type type)
           => Nullable.GetUnderlyingType(type) ?? type;

        protected static string GetOrDefault(string value, string defaultVale)
        {
            return string.IsNullOrWhiteSpace(value) ? defaultVale : value;
        }
    }
}
